package  com.text
{
	import flash.geom.Rectangle;
	import flash.text.TextField;
	import flash.text.TextLineMetrics;
	
	internal class StringBoundary extends Range
	{
		/**
		* The TextField the string is in.
		*/
		private var textField:TextField;
		
		/**
		* The first line of text the string inhabits. zero based index
		*/
		private var startLine:int;
		
		/**
		* The last line of text the string inhabits. zero based index
		*/
		private var endLine:int;
		 
		 
		/**
		* The horizontal offset to apply to the bounding rectangle.
		*/
		private var xOffset:Number;
		
		/**
		* The vertical offset to apply to the bounding rectangle.
		*/
		private var yOffset:Number;
		 public var content:String;
		/**
		* TextField.getCharBoundaries seems to be consistently innaccurate by this amount in the x-axis.
		*/
		public const X_CORRECTION:Number = 1;
		
		/**
		* TextField.getCharBoundaries seems to be consistently innaccurate by this amount in the y-axis.
		*/
		public const Y_CORRECTION:Number = 2;
		
		/**
		* Finds the bounding rectangle of a character range within a TextField object.  If the character range spans multiple lines, bounding rectangles are calculated for each line.
		* 
		* @param textField The TextField the string is in.
		* @param beginIndex The start index of the character range. zero based
		* @param endIndex The end index of the character range. 1 based or zero based after the last character  
		 * eg. a range spanning a single character at 0 ends at 1 which is consistent with adobe double index methods such as Substring, TextRange and Textfield methods
		* @param xOffset The horizontal offset to apply to the boundary rectangle.
		* @param yOffset The vertical offset to apply to the bounding rectangle.
		*/
		public function StringBoundary(textField:TextField,beginIndex:int,endIndex:int,xOffset:Number=0,yOffset:Number=0)
		{
			super(beginIndex,endIndex);
			this.textField = textField;
			this.xOffset = xOffset;
			this.yOffset = yOffset;
			this.content = textField.text.substring(beginIndex,endIndex);//substring is one based
		}
		
		/**
		* Returns the bounding rectangles of the current character range.
		*/
		public function get rectangles():Array{
			var rects:Array; 
			 
			// If the visible area of the TextField is larger than the actual character range, only return rects within the character range instead.
		    //These are zero based character indexes 
			var firstVisibleIndex:int = this.textField.getLineOffset(this.textField.scrollV-1);
			var lastVisibleIndex:int = this.getLineEndOffset(this.textField.bottomScrollV-1);
			
			firstVisibleIndex = Math.max(firstVisibleIndex, this.beginIndex);
			lastVisibleIndex = Math.min(lastVisibleIndex, this.endIndex-1);
				
			//zero based indexes of start and endlines		
			startLine = textField.getLineIndexOfChar(firstVisibleIndex);
			endLine = textField.getLineIndexOfChar(lastVisibleIndex);
	//		trace (" rectangles: lines ", startLine, " to ", endLine, " range " , firstVisibleIndex, lastVisibleIndex);
			// If the character range spans multiple lines, get bounding rects for each line.
			// Otherwise, just get one bounding rect for the whole character range.
			if(this.isMultiline){
				rects = this.getLineRects(Math.min(firstVisibleIndex, lastVisibleIndex),Math.max(lastVisibleIndex,firstVisibleIndex));
			}else{
				rects = [this.stringRect(firstVisibleIndex, lastVisibleIndex)]; 
			}
			
			return rects;
		}
		
		/**
		* Indicates whether or not the character range spans multiple lines.
		*/
		private function get isMultiline():Boolean{
			if(this.endLine > this.startLine){
				return true;
			}
			return false;
		}
		
		/**
		* Indicates whether or not the character range has visible characters.
		*/
		public function get isVisible():Boolean{
			var firstVisibleChar:int = this.textField.getLineOffset(this.textField.scrollV-1);//zero based 
			var lastVisibleChar:int = this.getLineEndOffset(this.textField.bottomScrollV-1);//zero based 
			
			if(this.endIndex-1 >= firstVisibleChar && this.beginIndex <= lastVisibleChar){
				return true;
			}
			return false;
		}
		
		/** 	
		* Returns an array of Rectangles representing the boundaries of a multiline character range.
		* 
		* @param beginIndex The zero index of the character range
		* @param endIndex The zero index of the character range
		*/
		private function getLineRects(beginIndex:int, endIndex:int):Array{
			var r:Array = new Array();
			
			var startLn:int = this.textField.getLineIndexOfChar(beginIndex);
			var endLn:int = this.textField.getLineIndexOfChar(endIndex);
			
			var numLines:int = endLn - startLn;
			
			var startLineEndOffset:int = this.getLineEndOffset(startLn);
			
			r.push(this.stringRect(beginIndex,startLineEndOffset));
			
			for(var i:int=1; i<numLines; i++){
				var line:int = startLn + i;
				var ind1:int = textField.getLineOffset(line);
				var ind2:int = this.getLineEndOffset(line);
				r.push(this.stringRect(ind1,ind2));
			}
			
			var endLineOffset:int = textField.getLineOffset(endLn);
			 
			if (endLineOffset <= endIndex)
			{
			   /* if (endIndex+1 == textField.length)
			      endIndex++; */	
			   r.push(this.stringRect(endLineOffset,endIndex));
			}
			return r;
		}
		
		/**	
		* Returns a Rectangle representing the boundaries of a singleline character range.
		* 
		* @param beginIndex The zero based index of the first character range
		* @param endIndex The zero based index of the last character range
		*/
		private function stringRect(beginIndex:int, endIndex:int):Rectangle{
			
			var rect:Rectangle ;
			
		    rect = this.getAdjustedCharBoundaries(beginIndex);
			//not single character
			if(beginIndex != endIndex)
			{
				var rect2:Rectangle = this.getAdjustedCharBoundaries(endIndex ); 
				rect = new Rectangle(rect.left, rect.top, rect2.right - rect.left, rect2.height);
			}
			//trace ("stringRect range", beginIndex, endIndex,"stringRect", rect.left, rect.top, rect.right, rect.bottom );
			return rect;
		}
		
		/**
		* Returns the zero based index of the last character in a line of text 
		*/
		private function getLineEndOffset(lineIndex:int):int
		{
			var lineOffset:int = textField.getLineOffset(lineIndex);
			var lineLength:int = textField.getLineLength(lineIndex);
			/* var char:String = textField.text.charAt(lineOffset + lineLength -1 ); 
			 *//*if (char =="\r")
			  lineLength -= 1; 
			 else
			  lineLength -= 2;  */
			return lineOffset + lineLength-1  ;
		}
        
        /**
        * Returns the Y position of a character's bounding rect in consideration of the vertical scroll position of the TextField.
        * <p>Because getCharBoundaries returns the same bounding rect regardless of the TextField's scroll position, it is necessary to adjust for it.</p> 
        * <p>adjustedCharY = the Y position of the character - the Y position of the first visible line of text.</p>
        */
/*         private function get adjustedCharY():int{
         */	
            /*
                Get the rect of the first visible character
            */
/*          var lineIndex:int = this.textField.scrollV-1;
            var ind:int = this.textField.getLineOffset(lineIndex);
            var rect:Rectangle = this.textField.getCharBoundaries(ind);
             */
            /*
                For some characters (such as carriage returns), getCharBoundaries returns null.
                In those situations, we find the next character that does not return null,
                sum the line heights up to that point, and subtract that sum from the y
                position of the character.
            */
       /*      var lineHeights:Number = 0;
            while(rect == null){
                lineHeights += this.textField.getLineMetrics(lineIndex).height;
                lineIndex++;
                if(lineIndex == (this.textField.maxScrollV+this.textField.bottomScrollV-1)){
                    rect = new Rectangle(0,0,0,0);
                    break;
                }
                rect = this.textField.getCharBoundaries(this.textField.getLineOffset(lineIndex));
            }
            rect.y -= lineHeights;

            return rect.y;
        } */
        
		/**
		* Returns the bounding rectangle of a character in consideration of the vertical scroll position of the TextField and other manual offsets. 
		* Also, prevents TextField.getCharBoundaries from returning null and causing an error.
		*
		* @param index The zero based index of the character to get boundaries for.
		* @param applyScrollVOffset Controls whether or not to adjust for the TextField's vertical scroll position.  
    	* Since calls to get adjustedCharY can be expensive, we only call it when necessary.
		*/
		private function getAdjustedCharBoundaries(index:int):Rectangle{
			var rect:Rectangle = this.textField.getCharBoundaries(index);
			var lineIndex:int = this.textField.getLineIndexOfChar(index);
			var lineMetrics:TextLineMetrics = this.textField.getLineMetrics(lineIndex);
			const GUTTER:int = 2;
			if(rect == null)
			{//some characters such as carriage return return null char boundaries
				if (index < 0 || index >= textField.text.length )
					throw new Error("Invalid index supplied to getAdjustedCharBoundares: " + index); 
				rect = new Rectangle(0,0,0,0);
				
				var lineStartCharIndex:int = this.textField.getLineOffset(lineIndex);
				var linelen:int = this.textField.getLineLength(lineIndex);
				var linetxt:String = this.textField.getLineText(lineIndex);
				
				//check if this is the first char on the line
				if (lineStartCharIndex == index)
				{   rect.left = GUTTER;//two is the 2 pixel gutter http://livedocs.adobe.com/flex/3/langref/flash/text/TextLineMetrics.html	
				}
				else 
				{  //get the boundaries of the preceding character
				   var rectPre:Rectangle = this.textField.getCharBoundaries(index-1);
				   rect.left = rectPre.right;
				} 
				//check if this is the last char in the line
				if (lineStartCharIndex + linelen - 1 ==index)
				{
				   rect.width = 3;//const size
				} 
  		        rect.top = lineMetrics.height * lineIndex + GUTTER ; //apprx cal of y position
			}
			rect.height = lineMetrics.height;//read the height from the line metric
			
			// corrections and offsets
			 rect.x += /* this.X_CORRECTION + */ this.xOffset;
			 rect.y += /* this.Y_CORRECTION + */ this.yOffset;
			if (!textField.wordWrap)//no wordwrap  textfield boundaries limit the rectangle
			{
				rect.left = ( rect.left > 0 ? rect.left : 0 );
				rect.right = (rect.right < this.textField.width ? rect.right:this.textField.width);
			}
			//trace ("unadjusted rectangle" , rect.x,rect.y , "verticalScrollPosition",CustomTextArea(textField.parent).verticalScrollPosition );
			//adjust the y postion if there is is scrolling.
			if(this.textField.scrollV >1  ){
				 var ind:int = this.textField.getLineOffset(this.textField.scrollV-1);
            	 var rectFstLine:Rectangle = this.textField.getCharBoundaries(ind);
            	 if (rectFstLine == null)
				 {
				 	var y:int = 0;
				 	for (var i:int=0; i < this.textField.scrollV -1; i++)
				    	 y+= this.textField.getLineMetrics(i).height;
				 	rect.y = rect.y - y;
				 }
				 else
				 {
				   rect.y = rect.y - rectFstLine.y
				 }	
			}
			 
			return rect;
		} 
		
		
		
		 
	}
}
